// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <ddk/debug.h>
#include <ddk/phys-iter.h>
#include <ddk/protocol/usb/hci.h>
#include <fbl/auto_lock.h>
#include <zircon/assert.h>
#include <zircon/hw/usb.h>
#include <stdio.h>
#include <string.h>
#include <threads.h>

#include "xhci-transfer.h"
#include "xhci-trb.h"
#include "xhci-util.h"

namespace usb_xhci {

// reads a range of bits from an integer
#define READ_FIELD(i, start, bits) (((i) >> (start)) & ((1 << (bits)) - 1))

// This resets the transfer ring's dequeue pointer just past the last completed transfer.
// This can only be called when the endpoint is stopped and we are locked on ep->lock.
static zx_status_t xhci_reset_dequeue_ptr_locked(xhci_t* xhci, uint32_t slot_id,
                                                 uint32_t ep_index) {
  xhci_slot_t* slot = &xhci->slots[slot_id];
  xhci_endpoint_t* ep = &slot->eps[ep_index];
  xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring;

  xhci_sync_command_t command;
  xhci_sync_command_init(&command);
  uint64_t ptr = xhci_transfer_ring_current_phys(transfer_ring);
  ptr |= transfer_ring->pcs;
  // command expects device context index, so increment ep_index by 1
  uint32_t control = (slot_id << TRB_SLOT_ID_START) | ((ep_index + 1) << TRB_ENDPOINT_ID_START);
  zx_status_t status =
      xhci_post_command(xhci, TRB_CMD_SET_TR_DEQUEUE, ptr, control, &command.context);
  if (status != ZX_OK) {
    return status;
  }
  int cc = xhci_sync_command_wait(&command);
  if (cc != TRB_CC_SUCCESS) {
    zxlogf(ERROR, "TRB_CMD_SET_TR_DEQUEUE failed cc: %d\n", cc);
    return ZX_ERR_INTERNAL;
  }
  xhci_set_dequeue_ptr(transfer_ring, transfer_ring->current_trb);

  return ZX_OK;
}

static void xhci_process_transactions_locked(xhci_t* xhci, xhci_slot_t* slot, uint8_t ep_index,
                                             list_node_t* completed_reqs);

zx_status_t xhci_reset_endpoint(xhci_t* xhci, uint32_t slot_id, uint8_t ep_address) {
  xhci_slot_t* slot = &xhci->slots[slot_id];
  uint8_t ep_index = xhci_endpoint_index(ep_address);
  xhci_endpoint_t* ep = &slot->eps[ep_index];
  usb_request_t* req;

  // Recover from Halted and Error conditions. See section 4.8.3 of the XHCI spec.

  ep->lock.Acquire();

  if (ep->state != EP_STATE_HALTED && ep->state != EP_STATE_ERROR) {
    ep->lock.Release();
    return ZX_OK;
  }

  int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
  zxlogf(TRACE, "xhci_reset_endpoint %d %d ep_ctx_state %d\n", slot_id, ep_index, ep_ctx_state);

  if (ep_ctx_state == EP_CTX_STATE_STOPPED || ep_ctx_state == EP_CTX_STATE_RUNNING) {
    ep->state = EP_STATE_RUNNING;
    ep->lock.Release();
    return ZX_OK;
  }
  if (ep_ctx_state == EP_CTX_STATE_HALTED) {
    // reset the endpoint to move from Halted to Stopped state
    xhci_sync_command_t command;
    xhci_sync_command_init(&command);
    // command expects device context index, so increment ep_index by 1
    uint32_t control = (slot_id << TRB_SLOT_ID_START) | ((ep_index + 1) << TRB_ENDPOINT_ID_START);
    zx_status_t status =
        xhci_post_command(xhci, TRB_CMD_RESET_ENDPOINT, 0, control, &command.context);
    if (status != ZX_OK) {
      ep->lock.Release();
      return status;
    }

    // Release the lock before waiting for the command. The command may not complete,
    // if there is another transfer event on the completer thread waiting for the lock
    // on the same endpoint.
    ep->lock.Release();
    int cc = xhci_sync_command_wait(&command);
    if (cc != TRB_CC_SUCCESS) {
      zxlogf(ERROR, "xhci_reset_endpoint: TRB_CMD_RESET_ENDPOINT failed cc: %d\n", cc);
      return ZX_ERR_INTERNAL;
    }
    ep->lock.Acquire();

    // calling USB_REQ_CLEAR_FEATURE on a stalled control endpoint will not work,
    // so we only do this for the other endpoints
    if (ep_address != 0) {
      // This should come after the successful completion of a Reset Endpoint Command.
      // See XHCI spec, section 4.6.8
      xhci_control_request(xhci, slot_id, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT,
                           USB_REQ_CLEAR_FEATURE, USB_ENDPOINT_HALT, ep_address, nullptr, 0,
                           nullptr);
    }
  }

  // resetting the dequeue pointer gets us out of ERROR state, and is also necessary
  // after TRB_CMD_RESET_ENDPOINT.
  if (ep_ctx_state == EP_CTX_STATE_ERROR || ep_ctx_state == EP_CTX_STATE_HALTED) {
    // move transfer ring's dequeue pointer passed the failed transaction
    zx_status_t status = xhci_reset_dequeue_ptr_locked(xhci, slot_id, ep_index);
    if (status != ZX_OK) {
      ep->lock.Release();
      return status;
    }
  }

  // xhci_reset_dequeue_ptr_locked will skip past all pending transactions,
  // so move them all to the queued list so they will be requeued
  // Completed these with ZX_ERR_CANCELED out of the lock.
  // Remove from tail and add to head to preserve the ordering
  while (xhci_remove_from_list_tail(xhci, &ep->pending_reqs, &req)) {
    xhci_add_to_list_head(xhci, &ep->queued_reqs, req);
  }

  ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
  zx_status_t status;
  switch (ep_ctx_state) {
    case EP_CTX_STATE_DISABLED:
      ep->state = EP_STATE_DEAD;
      status = ZX_ERR_IO_NOT_PRESENT;
      break;
    case EP_CTX_STATE_RUNNING:
    case EP_CTX_STATE_STOPPED:
      ep->state = EP_STATE_RUNNING;
      status = ZX_OK;
      break;
    case EP_CTX_STATE_ERROR:
      ep->state = EP_STATE_ERROR;
      status = ZX_ERR_IO_INVALID;
      break;
    case EP_CTX_STATE_HALTED:
      ep->state = EP_STATE_HALTED;
      status = ZX_ERR_IO_REFUSED;
      break;
    default:
      ep->state = EP_STATE_HALTED;
      status = ZX_ERR_INTERNAL;
      break;
  }

  list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
  if (ep->state == EP_STATE_RUNNING) {
    // start processing transactions again
    xhci_process_transactions_locked(xhci, slot, ep_index, &completed_reqs);
  }

  ep->lock.Release();

  xhci_usb_request_internal_t* req_int = nullptr;
  // call complete callbacks out of the lock
  while ((req_int = list_remove_head_type(&completed_reqs, xhci_usb_request_internal_t, node)) !=
         nullptr) {
    req = XHCI_INTERNAL_TO_USB_REQ(req_int);
    usb_request_complete(req, req->response.status, req->response.actual, &req_int->complete_cb);
  }

  return status;
}

// locked on ep->lock
static zx_status_t xhci_start_transfer_locked(xhci_t* xhci, xhci_slot_t* slot, uint32_t ep_index,
                                              usb_request_t* req) {
  xhci_endpoint_t* ep = &slot->eps[ep_index];
  xhci_transfer_ring_t* ring = &ep->transfer_ring;
  if (ep->state != EP_STATE_RUNNING) {
    zxlogf(ERROR, "xhci_start_transfer_locked bad ep->state %d\n", ep->state);
    return ZX_ERR_BAD_STATE;
  }

  if (req->header.length > 0) {
    zx_status_t status = usb_request_physmap(req, xhci->bti_handle.get());
    if (status != ZX_OK) {
      zxlogf(ERROR, "%s: usb_request_physmap failed: %d\n", __FUNCTION__, status);
      return status;
    }
  }

  xhci_transfer_state_t* state = ep->transfer_state;
  xhci_transfer_state_init(state, req, ep->ep_type, ep->max_packet_size);

  if (req->header.length > UINT32_MAX) {
    return ZX_ERR_BUFFER_TOO_SMALL;
  }
  auto length = static_cast<uint32_t>(req->header.length);
  uint32_t interrupter_target = 0;

  usb_setup_t* setup = (req->header.ep_address == 0 ? &req->setup : nullptr);
  if (setup) {
    // Setup Stage
    xhci_trb_t* trb = ring->current_trb;
    xhci_clear_trb(trb);

    XHCI_SET_BITS32(&trb->ptr_low, SETUP_TRB_REQ_TYPE_START, SETUP_TRB_REQ_TYPE_BITS,
                    setup->bmRequestType);
    XHCI_SET_BITS32(&trb->ptr_low, SETUP_TRB_REQUEST_START, SETUP_TRB_REQUEST_BITS,
                    setup->bRequest);
    XHCI_SET_BITS32(&trb->ptr_low, SETUP_TRB_VALUE_START, SETUP_TRB_VALUE_BITS, setup->wValue);
    XHCI_SET_BITS32(&trb->ptr_high, SETUP_TRB_INDEX_START, SETUP_TRB_INDEX_BITS, setup->wIndex);
    XHCI_SET_BITS32(&trb->ptr_high, SETUP_TRB_LENGTH_START, SETUP_TRB_LENGTH_BITS, length);
    XHCI_SET_BITS32(&trb->status, XFER_TRB_XFER_LENGTH_START, XFER_TRB_XFER_LENGTH_BITS, 8);
    XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
                    interrupter_target);

    uint32_t control_bits =
        (length == 0 ? XFER_TRB_TRT_NONE
                     : (state->direction == USB_DIR_IN ? XFER_TRB_TRT_IN : XFER_TRB_TRT_OUT));
    control_bits |= XFER_TRB_IDT;  // immediate data flag
    trb_set_control(trb, TRB_TRANSFER_SETUP, control_bits);
    if (driver_get_log_flags() & DDK_LOG_SPEW)
      xhci_print_trb(ring, trb);
    xhci_increment_ring(ring);
  }

  return ZX_OK;
}

// returns ZX_OK if req has been successfully queued,
// ZX_ERR_SHOULD_WAIT if we ran out of TRBs and need to try again later,
// or other error for a hard failure.
static zx_status_t xhci_continue_transfer_locked(xhci_t* xhci, xhci_slot_t* slot, uint32_t ep_index,
                                                 usb_request_t* req) {
  xhci_endpoint_t* ep = &slot->eps[ep_index];
  xhci_transfer_ring_t* ring = &ep->transfer_ring;

  usb_header_t* header = &req->header;
  xhci_transfer_state_t* state = ep->transfer_state;
  size_t length = header->length;
  size_t free_trbs = xhci_transfer_ring_free_trbs(&ep->transfer_ring);
  uint8_t direction = state->direction;
  bool isochronous = (ep->ep_type == USB_ENDPOINT_ISOCHRONOUS);
  uint64_t frame = header->frame;

  uint32_t interrupter_target = 0;

  if (isochronous) {
    if (length == 0)
      return ZX_ERR_INVALID_ARGS;
    if (xhci->num_interrupts > 1) {
      interrupter_target = ISOCH_INTERRUPTER;
    }
  }

  if (frame != 0) {
    if (!isochronous) {
      zxlogf(ERROR, "frame scheduling only supported for isochronous transfers\n");
      return ZX_ERR_INVALID_ARGS;
    }
    uint64_t current_frame = xhci_get_current_frame(xhci);
    if (frame < current_frame) {
      zxlogf(ERROR, "can't schedule transfer into the past\n");
      return ZX_ERR_INVALID_ARGS;
    }
    if (frame - current_frame >= 895) {
      // See XHCI spec, section 4.11.2.5
      zxlogf(ERROR, "can't schedule transfer more than 895ms into the future\n");
      return ZX_ERR_INVALID_ARGS;
    }
  }

  // need to clean the cache for both IN and OUT transfers, invalidate only for IN
  if (direction == USB_DIR_IN) {
    usb_request_cache_flush_invalidate(req, 0, header->length);
  } else {
    usb_request_cache_flush(req, 0, header->length);
  }

  zx_status_t status = xhci_queue_data_trbs(ring, state, req, interrupter_target, isochronous);
  if (status != ZX_OK) {
    return status;
  }

  if (state->needs_status) {
    if (free_trbs == 0) {
      // will need to do this later
      return ZX_ERR_SHOULD_WAIT;
    }

    // Status Stage
    xhci_trb_t* trb = ring->current_trb;
    xhci_clear_trb(trb);
    XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
                    interrupter_target);
    uint32_t control_bits =
        (direction == USB_DIR_IN && length > 0 ? XFER_TRB_DIR_OUT : XFER_TRB_DIR_IN);
    // generate an event for the status phase so we can catch stalls or other errors
    // before completing control transfer requests
    control_bits |= XFER_TRB_IOC;
    trb_set_control(trb, TRB_TRANSFER_STATUS, control_bits);
    if (driver_get_log_flags() & DDK_LOG_SPEW)
      xhci_print_trb(ring, trb);
    xhci_increment_ring(ring);
    free_trbs--;
    state->needs_status = false;
  }

  xhci_usb_request_internal_t* req_int = USB_REQ_TO_XHCI_INTERNAL(req);
  // if we get here, then we are ready to ring the doorbell
  // update dequeue_ptr to TRB following this transaction
  req_int->context = ring->current_trb;

  XHCI_WRITE32(&xhci->doorbells[header->device_id], ep_index + 1);
  // it seems we need to ring the doorbell a second time when transitioning from STOPPED
  while (xhci_get_ep_ctx_state(slot, ep) == EP_CTX_STATE_STOPPED) {
    zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
    XHCI_WRITE32(&xhci->doorbells[header->device_id], ep_index + 1);
  }

  return ZX_OK;
}

static void xhci_process_transactions_locked(xhci_t* xhci, xhci_slot_t* slot, uint8_t ep_index,
                                             list_node_t* completed_reqs) {
  xhci_endpoint_t* ep = &slot->eps[ep_index];

  // loop until we fill our transfer ring or run out of requests to process
  while (1) {
    if (xhci_transfer_ring_free_trbs(&ep->transfer_ring) == 0) {
      // no available TRBs - need to wait for some complete
      return;
    }

    while (!ep->current_req) {
      // start the next transaction in the queue
      usb_request_t* req;
      xhci_remove_from_list_head(xhci, &ep->queued_reqs, &req);
      if (!req) {
        // nothing to do
        return;
      }

      zx_status_t status = xhci_start_transfer_locked(xhci, slot, ep_index, req);
      if (status == ZX_OK) {
        xhci_add_to_list_tail(xhci, &ep->pending_reqs, req);
        ep->current_req = req;
      } else {
        req->response.status = status;
        req->response.actual = 0;
        xhci_add_to_list_tail(xhci, completed_reqs, req);
      }
    }

    if (ep->current_req) {
      usb_request_t* req = ep->current_req;
      zx_status_t status = xhci_continue_transfer_locked(xhci, slot, ep_index, req);
      if (status == ZX_ERR_SHOULD_WAIT) {
        // no available TRBs - need to wait for some complete
        return;
      } else {
        if (status != ZX_OK) {
          req->response.status = status;
          req->response.actual = 0;
          xhci_delete_req_node(xhci, req);
          xhci_add_to_list_tail(xhci, completed_reqs, req);
        }
        ep->current_req = nullptr;
      }
    }
  }
}

zx_status_t xhci_queue_transfer(xhci_t* xhci, usb_request_t* req) {
  uint32_t slot_id = req->header.device_id;
  uint8_t ep_index = xhci_endpoint_index(req->header.ep_address);
  __UNUSED usb_setup_t* setup = (ep_index == 0 ? &req->setup : nullptr);

  zxlogf(LSPEW, "xhci_queue_transfer slot_id: %d setup: %p ep_index: %d length: %lu\n", slot_id,
         setup, ep_index, req->header.length);

  int rh_index = xhci_get_root_hub_index(xhci, slot_id);
  if (rh_index >= 0) {
    return xhci_rh_usb_request_queue(xhci, req, rh_index);
  }

  if (slot_id < 1 || slot_id > xhci->max_slots) {
    return ZX_ERR_INVALID_ARGS;
  }
  if (ep_index >= XHCI_NUM_EPS) {
    return ZX_ERR_INVALID_ARGS;
  }

  xhci_slot_t* slot = &xhci->slots[slot_id];
  xhci_endpoint_t* ep = &slot->eps[ep_index];
  if (!slot->sc) {
    // slot no longer enabled
    return ZX_ERR_IO_NOT_PRESENT;
  }

  list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);

  {
    fbl::AutoLock al(&ep->lock);

    zx_status_t status;
    switch (ep->state) {
      case EP_STATE_DEAD:
        status = ZX_ERR_IO_NOT_PRESENT;
        break;
      case EP_STATE_RUNNING:
        status = ZX_OK;
        break;
      case EP_STATE_PAUSED:
        status = ZX_ERR_BAD_STATE;
        break;
      case EP_STATE_ERROR:
        status = ZX_ERR_IO_INVALID;
        break;
      case EP_STATE_HALTED:
        status = ZX_ERR_IO_REFUSED;
        break;
      case EP_STATE_DISABLED:
        status = ZX_ERR_BAD_STATE;
        break;
      default:
        status = ZX_ERR_INTERNAL;
        break;
    }

    if (status != ZX_OK) {
      return status;
    }

    xhci_add_to_list_tail(xhci, &ep->queued_reqs, req);

    xhci_process_transactions_locked(xhci, slot, ep_index, &completed_reqs);
  }

  xhci_usb_request_internal_t* req_int = nullptr;
  // call complete callbacks out of the lock
  while ((req_int = list_remove_head_type(&completed_reqs, xhci_usb_request_internal_t, node)) !=
         nullptr) {
    req = XHCI_INTERNAL_TO_USB_REQ(req_int);
    usb_request_complete(req, req->response.status, req->response.actual, &req_int->complete_cb);
  }

  return ZX_OK;
}

zx_status_t xhci_cancel_transfers(xhci_t* xhci, uint32_t slot_id, uint32_t ep_index) {
  zxlogf(TRACE, "xhci_cancel_transfers slot_id: %d ep_index: %d\n", slot_id, ep_index);

  if (slot_id < 1 || slot_id > xhci->max_slots) {
    return ZX_ERR_INVALID_ARGS;
  }
  if (ep_index >= XHCI_NUM_EPS) {
    return ZX_ERR_INVALID_ARGS;
  }

  xhci_slot_t* slot = &xhci->slots[slot_id];
  xhci_endpoint_t* ep = &slot->eps[ep_index];
  list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
  usb_request_t* req;
  zx_status_t status = ZX_OK;

  ep->lock.Acquire();

  if (ep->state == EP_STATE_HALTED) {
    // xhci_reset_endpoint will be issued, when the transaction
    // that caused the STALL is completed. Let xhci_reset_endpoint
    // take care of resetting the endpoint to a running state.
    ep->lock.Release();
    return status;
  }
  if (!list_is_empty(&ep->pending_reqs)) {
    // stop the endpoint and remove transactions that have already been queued
    // in the transfer ring
    ep->state = EP_STATE_PAUSED;

    xhci_sync_command_t command;
    xhci_sync_command_init(&command);
    // command expects device context index, so increment ep_index by 1
    uint32_t control = (slot_id << TRB_SLOT_ID_START) | ((ep_index + 1) << TRB_ENDPOINT_ID_START);
    zx_status_t status =
        xhci_post_command(xhci, TRB_CMD_STOP_ENDPOINT, 0, control, &command.context);
    if (status != ZX_OK) {
      ep->lock.Release();
      return status;
    }

    // We can't block on command completion while holding the lock.
    // It is safe to unlock here because no additional transactions will be
    // queued on the endpoint when ep->state is EP_STATE_PAUSED.
    ep->lock.Release();
    int cc = xhci_sync_command_wait(&command);
    if (cc != TRB_CC_SUCCESS) {
      // TRB_CC_CONTEXT_STATE_ERROR is normal here in the case of a disconnected device,
      // since by then the endpoint would already be in error state.
      zxlogf(ERROR, "xhci_cancel_transfers: TRB_CMD_STOP_ENDPOINT failed cc: %d\n", cc);
      return ZX_ERR_INTERNAL;
    }
    ep->lock.Acquire();

    // TRB_CMD_STOP_ENDPOINT may have have completed a currently executing request
    // but we may still have other pending requests. xhci_reset_dequeue_ptr_locked()
    // will set the dequeue pointer after the last completed request.
    while (xhci_remove_from_list_head(xhci, &ep->queued_reqs, &req)) {
      req->response.status = ZX_ERR_CANCELED;
      req->response.actual = 0;
      xhci_add_to_list_head(xhci, &completed_reqs, req);
    }

    status = xhci_reset_dequeue_ptr_locked(xhci, slot_id, ep_index);
    if (status == ZX_OK) {
      ep->state = EP_STATE_RUNNING;
    }
  }

  // elements of the queued_reqs list can simply be removed and completed.
  while (xhci_remove_from_list_head(xhci, &ep->queued_reqs, &req)) {
    req->response.status = ZX_ERR_CANCELED;
    req->response.actual = 0;
    xhci_add_to_list_head(xhci, &completed_reqs, req);
  }

  ep->lock.Release();

  xhci_usb_request_internal_t* req_int;
  // call complete callbacks out of the lock
  while ((req_int = list_remove_head_type(&completed_reqs, xhci_usb_request_internal_t, node)) !=
         NULL) {
    req = XHCI_INTERNAL_TO_USB_REQ(req_int);
    usb_request_complete(req, req->response.status, req->response.actual, &req_int->complete_cb);
  }

  return status;
}

static void xhci_control_complete(void* ctx, usb_request_t* req) {
  sync_completion_signal((sync_completion_t*)ctx);
}

zx_status_t xhci_control_request(xhci_t* xhci, uint32_t slot_id, uint8_t request_type,
                                 uint8_t request, uint16_t value, uint16_t index, void* data,
                                 uint16_t length, size_t* out_actual) {
  zxlogf(LTRACE,
         "xhci_control_request slot_id: %d type: 0x%02X req: %d value: %d index: %d "
         "length: %d\n",
         slot_id, request_type, request, value, index, length);

  // xhci_control_request is only used for reading first 8 bytes of the device descriptor,
  // so it makes sense to pool them.
  usb_request_t* req = usb_request_pool_get(&xhci->free_reqs, length);

  if (req == nullptr) {
    uint64_t total_req_size = sizeof(usb_request_t) + sizeof(xhci_usb_request_internal_t);
    auto status = usb_request_alloc(&req, length, 0, total_req_size);
    if (status != ZX_OK) {
      return status;
    }
  }

  usb_setup_t* setup = &req->setup;
  setup->bmRequestType = request_type;
  setup->bRequest = request;
  setup->wValue = value;
  setup->wIndex = index;
  setup->wLength = length;
  req->header.device_id = slot_id;

  bool out = !!((request_type & USB_DIR_MASK) == USB_DIR_OUT);
  if (length > 0 && out) {
    usb_request_copy_to(req, data, length, 0);
  }

  sync_completion_t completion;

  req->header.length = length;
  usb_request_complete_t complete = {
      .callback = xhci_control_complete,
      .ctx = &completion,
  };
  xhci_request_queue(xhci, req, &complete);
  auto status = sync_completion_wait(&completion, ZX_SEC(1));
  if (status == ZX_OK) {
    status = req->response.status;
  } else if (status == ZX_ERR_TIMED_OUT) {
    zxlogf(ERROR, "xhci_control_request ZX_ERR_TIMED_OUT\n");
    sync_completion_reset(&completion);
    status = xhci_cancel_transfers(xhci, slot_id, 0);
    if (status == ZX_OK) {
      sync_completion_wait(&completion, ZX_TIME_INFINITE);
      status = ZX_ERR_TIMED_OUT;
    }
  }
  zxlogf(TRACE, "xhci_control_transfer got %d\n", status);
  if (status == ZX_OK && out_actual != nullptr) {
    *out_actual = req->response.actual;

    if (length > 0 && !out) {
      usb_request_copy_from(req, data, req->response.actual, 0);
    }
  }

  if (usb_request_pool_add(&xhci->free_reqs, req) != ZX_OK) {
    zxlogf(TRACE, "xhci_control_transfer: Unable to add back request to the free pool\n");
    usb_request_release(req);
  }

  zxlogf(TRACE, "xhci_control_request returning %d\n", status);
  return status;
}

zx_status_t xhci_get_descriptor(xhci_t* xhci, uint32_t slot_id, uint8_t type, uint16_t value,
                                uint16_t index, void* data, uint16_t length, size_t* out_actual) {
  return xhci_control_request(xhci, slot_id, USB_DIR_IN | type | USB_RECIP_DEVICE,
                              USB_REQ_GET_DESCRIPTOR, value, index, data, length, out_actual);
}

void xhci_handle_transfer_event(xhci_t* xhci, xhci_trb_t* trb) {
  zxlogf(LTRACE, "xhci_handle_transfer_event: %08X %08X %08X %08X\n", ((uint32_t*)trb)[0],
         ((uint32_t*)trb)[1], ((uint32_t*)trb)[2], ((uint32_t*)trb)[3]);

  uint32_t control = XHCI_READ32(&trb->control);
  uint32_t status = XHCI_READ32(&trb->status);
  uint32_t slot_id = READ_FIELD(control, TRB_SLOT_ID_START, TRB_SLOT_ID_BITS);
  // ep_index is device context index, so decrement by 1 to get zero based index
  auto ep_index =
      static_cast<uint8_t>(READ_FIELD(control, TRB_ENDPOINT_ID_START, TRB_ENDPOINT_ID_BITS) - 1);
  xhci_slot_t* slot = &xhci->slots[slot_id];
  xhci_endpoint_t* ep = &slot->eps[ep_index];
  xhci_transfer_ring_t* ring = &ep->transfer_ring;

  uint32_t cc = READ_FIELD(status, EVT_TRB_CC_START, EVT_TRB_CC_BITS);
  uint32_t length = READ_FIELD(status, EVT_TRB_XFER_LENGTH_START, EVT_TRB_XFER_LENGTH_BITS);
  usb_request_t* req = nullptr;

  list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
  {
    fbl::AutoLock al(&ep->lock);
    zx_status_t result;
    switch (cc) {
      case TRB_CC_SUCCESS:
      case TRB_CC_SHORT_PACKET:
        result = length;
        break;
      case TRB_CC_BABBLE_DETECTED_ERROR:
        zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_BABBLE_DETECTED_ERROR\n");
        result = ZX_ERR_IO_OVERRUN;
        break;
      case TRB_CC_TRB_ERROR:
        zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_TRB_ERROR\n");
        int ep_ctx_state;
        ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
        /*
         * For usb-c ethernet adapters on Intel xhci controller, we receive this error
         * when a packet fails with NRDY token on the bus.see NET:97 for more details.
         * Slow down the requests in the client when this error is received.
         */
        if (ep_ctx_state == EP_CTX_STATE_ERROR) {
          result = ZX_ERR_IO_INVALID;
        } else {
          result = ZX_ERR_IO;
        }
        break;
      case TRB_CC_USB_TRANSACTION_ERROR:
      case TRB_CC_STALL_ERROR: {
        int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
        zxlogf(TRACE, "xhci_handle_transfer_event: cc %d ep_ctx_state %d\n", cc, ep_ctx_state);
        if (ep_ctx_state == EP_CTX_STATE_HALTED) {
          result = ZX_ERR_IO_REFUSED;
        } else {
          result = ZX_ERR_IO;
        }
        break;
      }
      case TRB_CC_RING_UNDERRUN:
        // non-fatal error that happens when no transfers are available for isochronous
        // endpoint
        zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_UNDERRUN\n");
        return;
      case TRB_CC_RING_OVERRUN:
        // non-fatal error that happens when no transfers are available for isochronous
        // endpoint
        zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_OVERRUN\n");
        return;
      case TRB_CC_MISSED_SERVICE_ERROR:
        zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_MISSED_SERVICE_ERROR\n");
        result = ZX_ERR_IO_MISSED_DEADLINE;
        break;
      case TRB_CC_STOPPED:
      case TRB_CC_STOPPED_LENGTH_INVALID:
      case TRB_CC_STOPPED_SHORT_PACKET:
      case TRB_CC_ENDPOINT_NOT_ENABLED_ERROR:
        switch (ep->state) {
          case EP_STATE_PAUSED:
            result = ZX_ERR_CANCELED;
            break;
          case EP_STATE_DISABLED:
            result = ZX_ERR_BAD_STATE;
            break;
          case EP_STATE_DEAD:
            result = ZX_ERR_IO_NOT_PRESENT;
            break;
          default:
            zxlogf(ERROR, "xhci_handle_transfer_event: bad state for stopped req: %d\n", ep->state);
            result = ZX_ERR_INTERNAL;
        }
        break;
      default: {
        int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
        zxlogf(ERROR,
               "xhci_handle_transfer_event: unhandled transfer event condition"
               " code %d  ep_ctx_state %d:  %08X %08X %08X %08X\n",
               cc, ep_ctx_state, ((uint32_t*)trb)[0], ((uint32_t*)trb)[1], ((uint32_t*)trb)[2],
               ((uint32_t*)trb)[3]);
        if (ep_ctx_state == EP_CTX_STATE_HALTED) {
          result = ZX_ERR_IO_REFUSED;
        } else if (ep_ctx_state == EP_CTX_STATE_ERROR) {
          result = ZX_ERR_IO_INVALID;
        } else {
          result = ZX_ERR_IO;
        }
        break;
      }
    }

    bool req_status_set = false;

    if (trb->ptr && !list_is_empty(&ep->pending_reqs) && ep->state != EP_STATE_DISABLED &&
        ep->state != EP_STATE_DEAD) {
      if (control & EVT_TRB_ED) {
        req = reinterpret_cast<usb_request_t*>(trb->ptr);
        if (ep_index == 0) {
          // For control requests we are expecting a second transfer event to signal the
          // end of the status phase. So here we record the status and actual for the
          // data phase but wait for the status phase to complete before completing the
          // request.
          slot->current_ctrl_req = req;
          if (result < 0) {
            req->response.status = result;
            req->response.actual = 0;
          } else {
            req->response.status = 0;
            req->response.actual = result;
          }
          return;
        }
      } else {
        trb = xhci_read_trb_ptr(ring, trb);
        if (trb_get_type(trb) == TRB_TRANSFER_STATUS && slot->current_ctrl_req) {
          // complete current control request
          req = slot->current_ctrl_req;
          slot->current_ctrl_req = nullptr;
          if (result < 0) {
            // sometimes we receive stall errors in the status phase so update
            // request status if necessary
            req->response.status = result;
            req->response.actual = 0;
          }
          req_status_set = true;
        } else {
          for (uint i = 0; i < TRANSFER_RING_SIZE && trb; i++) {
            if (trb_get_type(trb) == TRB_TRANSFER_EVENT_DATA) {
              req = reinterpret_cast<usb_request_t*>(trb->ptr);
              break;
            }
            trb = xhci_get_next_trb(ring, trb);
          }
        }
      }
    }

    int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
    if (ep_ctx_state != EP_CTX_STATE_RUNNING) {
      zxlogf(TRACE, "xhci_handle_transfer_event: ep ep_ctx_state %d cc %d\n", ep_ctx_state, cc);
    }

    if (!req) {
      // no req expected for this condition code
      if (cc != TRB_CC_STOPPED_LENGTH_INVALID) {
        zxlogf(TRACE, "xhci_handle_transfer_event: unable to find request to complete!\n");
      }
      return;
    }

    // When transaction errors occur, we sometimes receive multiple events for the same
    // transfer. Here we check to make sure that this event doesn't correspond to a transfer
    // that has already been completed. In the typical case, the context will be found at the
    // head of pending_reqs.
    bool found_req = false;
    usb_request_t* test;
    xhci_usb_request_internal_t* req_int = nullptr;
    list_for_every_entry (&ep->pending_reqs, req_int, xhci_usb_request_internal_t, node) {
      test = XHCI_INTERNAL_TO_USB_REQ(req_int);
      if (test == req) {
        found_req = true;
        break;
      }
    }
    if (!found_req) {
      zxlogf(TRACE,
             "xhci_handle_transfer_event: ignoring transfer event for completed "
             "transfer\n");
      return;
    }

    // update dequeue_ptr to TRB following this transaction
    xhci_set_dequeue_ptr(ring, req_int->context);

    // remove request from pending_reqs
    xhci_delete_req_node(xhci, req);

    if (!req_status_set) {
      if (result < 0) {
        req->response.status = result;
        req->response.actual = 0;
      } else {
        req->response.status = 0;
        req->response.actual = result;
      }
    }

    xhci_add_to_list_head(xhci, &completed_reqs, req);

    if (result == ZX_ERR_IO_REFUSED && ep->state != EP_STATE_DEAD) {
      ep->state = EP_STATE_HALTED;
    } else if (result == ZX_ERR_IO_INVALID && ep->state != EP_STATE_DEAD) {
      ep->state = EP_STATE_ERROR;
    } else if (ep->state == EP_STATE_RUNNING) {
      xhci_process_transactions_locked(xhci, slot, ep_index, &completed_reqs);
    }
  }

  // call complete callbacks out of the lock
  xhci_usb_request_internal_t* req_int;
  while ((req_int = list_remove_head_type(&completed_reqs, xhci_usb_request_internal_t, node)) !=
         NULL) {
    req = XHCI_INTERNAL_TO_USB_REQ(req_int);
    usb_request_complete(req, req->response.status, req->response.actual, &req_int->complete_cb);
  }
}

}  // namespace usb_xhci
